package com.bes.bessdk.connect;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.util.Log;

import com.bes.bessdk.BesSdkConstants;
import com.bes.bessdk.scan.BtHeleper;
import com.bes.bessdk.service.base.BesServiceConfig;
import com.bes.bessdk.utils.ArrayUtil;
import com.bes.bessdk.utils.LogUtils;
import com.bes.bessdk.utils.SPHelper;
import com.bes.sdk.connect.DeviceConnector;
import com.bes.sdk.device.HmDevice;
import com.bes.sdk.message.BaseMessage;
import com.bes.sdk.utils.DeviceProtocol;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import static com.bes.bessdk.BesSdkConstants.BES_SAVE_LOG_OTA;
import static com.bes.bessdk.BesSdkConstants.BES_SPP_CONNECT;
import static com.bes.bessdk.BesSdkConstants.BesConnectState;
import static com.bes.bessdk.BesSdkConstants.BesConnectState.BES_CONFIG_ERROR;
import static com.bes.bessdk.BesSdkConstants.BesConnectState.BES_CONNECT;
import static com.bes.bessdk.BesSdkConstants.BesConnectState.BES_CONNECT_NOTOTA;
import static com.bes.bessdk.BesSdkConstants.BesConnectState.BES_CONNECT_TOTA;
import static com.bes.bessdk.BesSdkConstants.BesConnectState.BES_NO_CONNECT;
import static com.bes.sdk.utils.DeviceProtocol.PROTOCOL_BLE;
import static com.bes.sdk.utils.DeviceProtocol.PROTOCOL_SPP;

public class SppConnector implements DeviceConnector {
    private final String TAG = getClass().getSimpleName();

    private static volatile SppConnector sConnector;
    private static Context mContext;

    private static List<BluetoothSocket> mBluetoothSockets;
    private static List<ConnectionListener> mConnectListeners;
    private static List<BesServiceConfig> mServiceConfigs;
    private static List<HmDevice> mHmDevices;
    private static List<ConnectedRunnable> mConnectedRunnables;
    ConnectedRunnable mConnectedRunnable;
    private static List<byte[]> mTotaAesKeys;

    private Object mListenerLock = new Object();

    private int reConnectTimes = 0;


    public static SppConnector getsConnector(Context context, BesServiceConfig serviceConfig) {
        if (sConnector == null) {
            synchronized (SppConnector.class) {
                if (sConnector == null) {
                    sConnector = new SppConnector();
                    mBluetoothSockets = new ArrayList<>();
                    mConnectListeners = new ArrayList<>();
                    mServiceConfigs = new ArrayList<>();
                    mHmDevices = new ArrayList<>();
                    mConnectedRunnables = new ArrayList<>();
                    mTotaAesKeys = new ArrayList<>();
                }
            }
        }
        if (context != null) {
            mContext = context;
        }
        if (serviceConfig != null && serviceConfig.getDevice() != null && sConnector.isNewDeviceWithConfig(serviceConfig)) {
            mHmDevices.add(serviceConfig.getDevice());
            mServiceConfigs.add(serviceConfig);
            mConnectListeners.add(null);
            mBluetoothSockets.add(null);
            mConnectedRunnables.add(null);
            mTotaAesKeys.add(null);
        }
        if (serviceConfig != null) {
            ArrayUtil.addObjectIndex(mServiceConfigs, serviceConfig, sConnector.getCurIndex(sConnector.getCurDevice(serviceConfig.getDevice())));
        }
        return sConnector;
    }

    public ArrayList<HmDevice> getCurConnectDevices() {
        ArrayList<HmDevice> curConDevices = new ArrayList<>();
        for (int i = 0; i < mHmDevices.size(); i ++) {
            BesServiceConfig config = mServiceConfigs.get(i);
            if (mConnectedRunnables.get(i) != null && config != null && (config.getTotaConnect() ? mTotaAesKeys.get(i) != null : true)) {
                curConDevices.add(mHmDevices.get(i));
            }
        }
        return curConDevices;
    }

    public BesConnectState getDeviceConnectState(BesServiceConfig serviceConfig) {
        if (serviceConfig.getDevice() == null || serviceConfig.getServiceUUID() == null) {
            return BES_CONFIG_ERROR;
        }
        BluetoothDevice device = getCurDevice(serviceConfig.getDevice());
        BesServiceConfig oldConfig = getCurServiceConfig(device);
        if (!isNewDeviceWithConfig(serviceConfig) && getCurBluetoothSocket(device) != null && getCurConnectedRunnable(device) != null && oldConfig != null && (oldConfig.getTotaConnect() ? getCurTotaAesKey(device) != null : true)) {
            if (serviceConfig.getServiceUUID().toString().equals(oldConfig.getServiceUUID().toString())) {
                return BES_CONNECT;
            }
            return oldConfig.getTotaConnect() ? BES_CONNECT_TOTA : BES_CONNECT_NOTOTA;
        }
        return BES_NO_CONNECT;
    }

    private boolean isNewDeviceWithConfig(BesServiceConfig serviceConfig) {
        HmDevice hmDevice = serviceConfig.getDevice();
        for (int i = 0; i < mHmDevices.size(); i ++) {
            HmDevice oldDevice = mHmDevices.get(i);
            if (serviceConfig.getDeviceProtocol() == PROTOCOL_SPP && hmDevice.getDeviceMAC().equals(oldDevice.getDeviceMAC())) {
                return false;
            }
        }
        return true;
    }

    private int getCurIndex(BluetoothDevice device) {
        for (int i = 0; i < mHmDevices.size(); i ++) {
            HmDevice hmDevice = mHmDevices.get(i);
            String address = hmDevice.getPreferredProtocol() == PROTOCOL_BLE ? hmDevice.getBleAddress() : hmDevice.getDeviceMAC();
            if (address.equals(device.getAddress())) {
                return i;
            }
        }
        return 10000;
    }

    public void refreshConnectListener(ConnectionListener connectionListener, BluetoothDevice device) {
        ArrayUtil.addObjectIndex(mConnectListeners, connectionListener, getCurIndex(device));
    }

    public void saveTotaAesKey(byte[] data, BluetoothDevice device) {
        ArrayUtil.addObjectIndex(mTotaAesKeys, data, getCurIndex(device));
    }

    public byte[] getTotaAesKey(BluetoothDevice device) {
        return getCurTotaAesKey(device);
    }

    private byte[] getCurTotaAesKey(BluetoothDevice device) {
        if (mTotaAesKeys.size() < getCurIndex(device)) {
            return null;
        }
        return mTotaAesKeys.get(getCurIndex(device));
    }

    private BluetoothDevice getCurDevice(HmDevice hmDevice) {
        String address = hmDevice.getPreferredProtocol() == PROTOCOL_BLE ? hmDevice.getBleAddress() : hmDevice.getDeviceMAC();
        BluetoothDevice device = BtHeleper.getBluetoothAdapter(mContext).getRemoteDevice(address);
        return device;
    }

    private HmDevice getCurHmDevice(BluetoothDevice device) {
        if (mHmDevices.size() < getCurIndex(device)) {
            return null;
        }
        return mHmDevices.get(getCurIndex(device));
    }

    private ConnectedRunnable getCurConnectedRunnable(BluetoothDevice device) {
        if (mConnectedRunnables.size() < getCurIndex(device)) {
            return null;
        }
        return mConnectedRunnables.get(getCurIndex(device));
    }

    private BluetoothSocket getCurBluetoothSocket(BluetoothDevice device) {
        if (mBluetoothSockets.size() < getCurIndex(device)) {
            return null;
        }
        return mBluetoothSockets.get(getCurIndex(device));
    }

    private BesServiceConfig getCurServiceConfig(BluetoothDevice device) {
        if (mServiceConfigs.size() < getCurIndex(device)) {
            return null;
        }
        return mServiceConfigs.get(getCurIndex(device));
    }

    private ConnectionListener getCurListener(BluetoothDevice device) {
        if (mConnectListeners.size() < getCurIndex(device)) {
            return null;
        }
        return mConnectListeners.get(getCurIndex(device));
    }
    
    @Override
    public List<DeviceProtocol> getSupportedProtocols() {
        List<DeviceProtocol> sL = new ArrayList<>();
        sL.add(PROTOCOL_SPP);
        return sL;
    }

    @Override
    public boolean isProtocolSupported(DeviceProtocol deviceProtocol) {
        if (deviceProtocol == PROTOCOL_SPP) {
            return true;
        }
        return false;
    }

    @Override
    public void connect(HmDevice device) {
        if (device == null || mContext == null) {
            return;
        }
        reConnectTimes = 0;
        startConnect(null, device);
    }

    @Override
    public void connect(HmDevice device, ConnectionListener connectionListener) {
        if (device == null || connectionListener == null || mContext == null) {
            return;
        }
        reConnectTimes = 0;
        startConnect(connectionListener, device);
    }

    private void startConnect(ConnectionListener connectionListener, HmDevice hmDevice) {

        LOG(TAG, "startConnect: -----spp");
        BluetoothDevice mDevice = BtHeleper.getBluetoothAdapter(mContext).getRemoteDevice(hmDevice.getDeviceMAC());
        BesServiceConfig serviceConfig = getCurServiceConfig(mDevice);
        if (serviceConfig == null) {
            onConnectionStateChanged(false, mDevice);
            return;
        }
        new Thread(new ConnectRunnable(mDevice, serviceConfig.getServiceUUID())).start();
        if (connectionListener != null) {
            ArrayUtil.addObjectIndex(mConnectListeners, connectionListener, getCurIndex(mDevice));
        }
    }

    private void onConnectionStateChanged(boolean connected, BluetoothDevice device) {
        Log.i(TAG, "onConnectionStateChanged: -----------" + connected);
        ConnectionListener connectionListener = getCurListener(device);
        if (connectionListener != null) {
            connectionListener.onStatusChanged(getCurHmDevice(device), connected ? BesSdkConstants.BES_CONNECT_SUCCESS : BesSdkConstants.BES_CONNECT_ERROR, PROTOCOL_SPP);
        }
        if (!connected) {
            close(device);
        }
    }

    public boolean write(byte[] data, BluetoothDevice device) {
        LOG(TAG, "spp write: -------" + data.length);
        LOG(TAG, "spp write: -------" + ArrayUtil.toHex(data));
//        ConnectedRunnable connectedRunnable = getCurConnectedRunnable(device);
        if (mConnectedRunnable != null) {
//            LOG(TAG, "spp write: -------" + ArrayUtil.toHex(data));
            return mConnectedRunnable.write(data);
        }
        return false;
    }

    private void onReceive(byte[] data, BluetoothDevice device) {
        LOG(TAG, "spp onReceive: -------" + device.getAddress());
        LOG(TAG, "spp onReceive: -------" + data.length);
        LOG(TAG, "spp onReceive: ------" + ArrayUtil.toHex(data));

        ConnectionListener connectionListener = getCurListener(device);
        if (connectionListener != null) {
            byte[] aesKey = getCurTotaAesKey(device);
            if (aesKey != null) {
                LOG(TAG, "spp onReceive: aesKey------" + ArrayUtil.toHex(aesKey));
            }
            if (aesKey != null && aesKey.length > 0) {
                String dataHex = ArrayUtil.toHex(data);
                String head = "48,45,41,44,";
                String tail = "54,41,49,4c,";
                String b_9f = "9f,";
                String markStr = tail + head;
                boolean isUseOtaService = false;
                if (data[0] == (byte) 0x9F) {
                    isUseOtaService = true;
                    markStr = tail + b_9f;
                }
                String[] dataArr = dataHex.split(markStr);
                LOG(TAG, "spp onReceive: dataArr------" + dataArr);
                LOG(TAG, "spp onReceive: dataArr------" + dataArr.length);

                if (dataArr.length > 1) {
                    for (int i = 0; i < dataArr.length; i ++) {
                        String lastData = "";
                        if (i == 0) {
                            lastData = dataArr[i] + tail;
                        } else if (i == dataArr.length - 1) {
                            lastData = (isUseOtaService ? b_9f : head) + dataArr[i];
                        } else {
                            lastData = (isUseOtaService ? b_9f : head) + dataArr[i] + tail;
                        }
                        LOG(TAG, "spp onReceive: lastData------" + lastData);
                        BaseMessage baseMessage = new BaseMessage();
                        baseMessage.setPush(true);
                        baseMessage.setMsgContent(ArrayUtil.toBytes(lastData));
                        connectionListener.onDataReceived(baseMessage);
                    }
                    return;
                }
            }
            BaseMessage baseMessage = new BaseMessage();
            baseMessage.setPush(true);
            baseMessage.setMsgContent(data);
            connectionListener.onDataReceived(baseMessage);
        }
    }
    
    @Override
    public void disconnect(HmDevice device) {
        LOG(TAG, "disconnect------spp");
        close(getCurDevice(device));
    }

    @Override
    public void registerConnectionListener(ConnectionListener connectionListener) {
        synchronized (mListenerLock) {
            if (!mConnectListeners.contains(connectionListener)) {
                mConnectListeners.add(connectionListener);
            }
        }
    }

    @Override
    public void unregisterConnectionListener(ConnectionListener connectionListener) {
        if (mConnectListeners.contains(connectionListener)) {
            mConnectListeners.remove(connectionListener);
        }
    }

    public void close(BluetoothDevice device) {
        try {
            BluetoothSocket bluetoothSocket = getCurBluetoothSocket(device);
            if (bluetoothSocket != null) {
                bluetoothSocket.close();
                bluetoothSocket = null;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        ArrayUtil.resetObjectAtIndex(mServiceConfigs, getCurIndex(device));
        ArrayUtil.resetObjectAtIndex(mBluetoothSockets, getCurIndex(device));
        ArrayUtil.resetObjectAtIndex(mConnectedRunnables, getCurIndex(device));
    }


    @SuppressLint("MissingPermission")
    private class ConnectRunnable implements Runnable {
        private BluetoothDevice mDevice;
        private UUID sUUID;

        public ConnectRunnable(BluetoothDevice device, UUID uuid) {
            mDevice = device;
            sUUID = uuid;
        }

        @Override
        public void run() {
            try {
                LOG(TAG, "run: ------" + sUUID);
                BluetoothSocket mBluetoothSocket = mDevice.createInsecureRfcommSocketToServiceRecord(sUUID);
                mBluetoothSocket.connect();
                mConnectedRunnable = new ConnectedRunnable(mBluetoothSocket.getInputStream(), mBluetoothSocket.getOutputStream(), mDevice);
                ArrayUtil.addObjectIndex(mBluetoothSockets, mBluetoothSocket, getCurIndex(mDevice));
                ArrayUtil.addObjectIndex(mConnectedRunnables, mConnectedRunnable, getCurIndex(mDevice));
                reConnectTimes = 100;
                onConnectionStateChanged(true, mDevice);
                new Thread(mConnectedRunnable).start();
            } catch (IOException e) {
                e.printStackTrace();
                LOG(TAG, "reConnectTimes: ------" + reConnectTimes);
                if (reConnectTimes > 2) {
                    onConnectionStateChanged(false, mDevice);
                } else {
                    reConnectTimes ++;
                    HmDevice device = getCurHmDevice(mDevice);
                    if (device != null) {
                        startConnect(null, device);
                    }
                }
            }
        }
    }

    private class ConnectedRunnable implements Runnable {

        private OutputStream mWrite;
        private InputStream mRead;
        private BluetoothDevice mDevice;

        public ConnectedRunnable(InputStream read, OutputStream write, BluetoothDevice device) {
            mRead = read;
            mWrite = write;
            mDevice = device;
        }

        @Override
        public void run() {
            try {
                byte[] data = new byte[1024 * 1024];
                while (true) {
                    int length = mRead.read(data);
                    onReceive(ArrayUtil.extractBytes(data, 0, length), mDevice);
                }
            } catch (IOException e) {
                e.printStackTrace();
                onConnectionStateChanged(false, mDevice);
            } finally {
                try {
                    if (mRead != null)
                        mRead.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        public boolean write(byte[] data) {
            try {
                mWrite.write(data);
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                onConnectionStateChanged(false, mDevice);
            }
            return false;
        }
    }

    public void LOG(String TAG, String msg) {
        if (mContext == null) {
            return;
        }
        Log.i(TAG, "LOG: " + msg);
        String saveLogName = (String) SPHelper.getPreference(mContext, BesSdkConstants.BES_SAVE_LOG_NAME, "");
        boolean saveLog = (boolean) SPHelper.getPreference(mContext, BesSdkConstants.BES_SAVE_LOG_KEY, BesSdkConstants.BES_SAVE_LOG_VALUE);
        if (saveLog) {
            if (saveLogName.equals(BES_SAVE_LOG_OTA)) {
                LogUtils.writeForOTA(TAG, msg, saveLogName);
            } else if (saveLogName.length() > 0){
                LogUtils.writeForLOG(TAG, msg, saveLogName);
            }
        }
    }

}
